/*
  判题核心
*/

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/dir.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/ptrace.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <map>
#include <memory>
#include <stdexcept>
#include <string>

#include "common.h"
#include "config.h"
#include "helper.h"
#include "language.h"
#include "logger.h"
#include "mjson.h"
#include "syscall.h"

// =============== 全局参数 ===============

// 寄存器别名
#define REG_SYSCALL orig_rax
#define REG_RET rax
#define REG_ARG0 rdi
#define REG_ARG1 rsi

// ================== 全局变量 ===================

std::string gSrcInFile;    // 原始输入文件的位置
std::string gDstInFile;    // 拷贝后输入文件的位置
std::string gUserOutFile;  // 用户输出
std::string gAndOutFile;   // 标准答案输出

int gLanguage;        // 所用编程语言
int gRunnerId = 0;    // judger 实例编号。
int gSolutionId = 0;  // 用户代码编号
int gProblemId = 0;   // 题号
int gJudgeResult;     // 评测结果
pid_t gUserPid;       // 用户代码进程 pid

std::string gUserCodeWorkDir;  // 用户代码工作目录
std::string gUserCodeSrcPath;  // 用户代码原始路径，从这里拷贝到工作目录

int mq_id = -1;

int gTotalTestCount;   // 当前 problem 的测试点总数
int gPassedTestCount;  // 已通过的 test case 数

// =================== 辅助函数 ======================

void reportJudgerStatusToMq(bool is_judge_over) {
  JudgeStatusMsg judge_stat;
  judge_stat.RunnerId = gRunnerId;
  judge_stat.ProblemId = gProblemId;
  judge_stat.SolutionId = gSolutionId;
  judge_stat.PassedTestCount = gPassedTestCount;
  judge_stat.LastUpdateTime = time(nullptr);
  judge_stat.IsJudgeOver = is_judge_over;
  judge_stat.JudgeResult = oj_err_code_map[gJudgeResult];
  judge_stat.TotalTestCount = gTotalTestCount;

  // 先转成 json
  json j_str = judge_stat;
  MsgBuffer msg;
  msg.mtype = MSG_TYPE;  // 必须指定，不然收不到
  strncpy(msg.mtext, j_str.dump().c_str(), sizeof(msg.mtext));

  if (msgsnd(mq_id, (void*)&msg, sizeof(msg.mtext), IPC_NOWAIT) == -1) {
    LOG_FATAL("msgsnd failed, %s", strerror(errno));
    exit(EXIT_FAILURE);
  } else {
    LOGD("report to mq: %s", msg.mtext);
  }
}

int limitProcessResource(int on_cpu_time, int mem) {
  int ret = 0;

  struct rlimit limit;
  // 限制 on-cpu 时间
  limit.rlim_max = on_cpu_time;
  limit.rlim_cur = on_cpu_time;
  ret = setrlimit(RLIMIT_CPU, &limit);
  if (ret) {
    return ret;
  }

  // 限制 off-cpu 时间
  // 前面用 on-cpu 时间做限制是为了防止进程调度造成用户代码实际得到的时间片比预期小
  // 这里限制 off-cpu 是为了防止用户用 sleep 等代码来卡 bug，所以额外限制下做一层保证
  alarm(5 * on_cpu_time);

  // 限制内存占用
  // FIXME: javac 限制内存会报 Error occurred during initialization of VM,
  // Could not allocate metaspace: 1073741824 bytes
  if (gLanguage != LANG_JAVA) {
    limit.rlim_max = mem;
    limit.rlim_cur = mem;
    ret = setrlimit(RLIMIT_AS, &limit);
    if (ret) {
      return ret;
    }
  }

  // LOGD("limit succ, cpu time %d\tmem %d", on_cpu_time, mem);
  return ret;
}

int compile(const char* work_dir) {
  int ret = 0;

  LOGD("workdir: %s", work_dir);

  // 编译开始，更新状态
  gJudgeResult = OJ_CI;

  // fork 分工
  pid_t child_pid = fork();
  if (child_pid == 0) {
    // 子进程负责编译

    ret = limitProcessResource(COMPILE_TIME_LIMIT, COMPILE_MEM_LIMIT);
    if (ret) {
      LOG_FATAL("limitProcessResource fail, ret %d", ret);
      gJudgeResult = OJ_SE;
      return -1;
    }

    // stderr >> ce.txt
    FILE* fp = freopen(CE_FILENAME, "w", stderr);
    if (fp == nullptr) {
      LOG_FATAL("freopen fail");
      gJudgeResult = OJ_SE;
      return -1;
    }

    // TODO: chroot & umount 限制使用第三方库

    LOGD("compile start");

    // compile
    ret = execvp(languages[gLanguage].compile_cmd[0], (char* const*)languages[gLanguage].compile_cmd);
    if (ret) {
      LOG_FATAL("execvp fail, cmd %s", languages[gLanguage].compile_cmd[0]);
      gJudgeResult = OJ_SE;
      return -1;
    }

    LOG_FATAL("should never run here");
  } else {
    // 父进程负责监控编译结果

    // TODO: umount

    int child_stat = 0;
    ret = waitpid(child_pid, &child_stat, 0);
    if (ret < 0) {
      LOG_FATAL("waitpid compile failed, ret %d, %s", ret, strerror(errno));
      gJudgeResult = OJ_SE;
      return ret;
    }
    // 编译成功的条件：
    // 1. 编译器正常退出（main 结束或者主动调用 exit）
    if (!WIFEXITED(child_stat)) {
      if (WIFSIGNALED(child_stat)) {
        // 因为信号退出
        int sig = WTERMSIG(child_stat);
        LOGD("compiler terminated by sig %d: %s", sig, strsignal(sig));
      } else {
        // 因为未知原因退出
        LOGD("compiler non-normal exit becase unknown reason");
      }
      // 非正常退出可能是因为用户提交了模板炸弹之类的代码，让编译器 TLE 或者 OOM 了。
      // 此时算作 CE，而不算 SE
      gJudgeResult = OJ_CE;
      return child_stat;
    }
    // 2. 编译器退出时返回码 == 0
    else if (WEXITSTATUS(child_stat) != 0) {
      int exit_code = WEXITSTATUS(child_stat);
      LOGD("compiler exit code != 0: %d, see the %sce.txt", exit_code, gUserCodeWorkDir.c_str());
      gJudgeResult = OJ_CE;
      return exit_code;
    }
    // 3. 没有输出编译错误信息
    auto ce_file_size = getFileSize(CE_FILENAME);
    if (ce_file_size != 0) {
      LOGD("compile error!");
      gJudgeResult = OJ_CE;
      return ce_file_size;
    }

    // 编译成功，转移到 running 状态
    LOGD("compile succ");
    gJudgeResult = OJ_RI;
    return 0;
  }

  LOG_FATAL("should never run here");
  return -1;
}

int diff(const char* file1, const char* file2) {
  // 本质上调用 Linux 的 diff 命令
  // 加 -w 忽略掉多余的空格
  std::string cmd = strFormat("/usr/bin/diff -w %s %s", file1, file2);
  int ret = system(cmd.c_str());
  return ret;
}

// 过滤 .in 后缀名的文件
int inputFileFilter(const struct dirent* entry) {
  int len = strlen(entry->d_name);
  if (len <= 3 || strcmp(entry->d_name + len - 3, ".in") != 0) {
    return 0;
  } else {
    return len - 3;
  }
}

bool syscall_white_map[CALL_ARRAY_SIZE];

// unsigned int syscall_counter[CALL_ARRAY_SIZE];
void initSyscallCouter() {
  for (int i = 0; i < CALL_ARRAY_SIZE; i++) {
    syscall_white_map[i] = false;
  }
  for (int i = 0, call_id = 0; (call_id = SYSCALL_WHITELIST[gLanguage][i]) != -1; i++) {
    // LOGD("set allow syscall %d", call_id);
    syscall_white_map[call_id] = true;
  }
}

// 检查 test case 文件，为运行阶段做准备
int prepareTestCaseDataFile(int case_id) {
  int ret = 0;

  // 1. 构造后面要用的几个全局 file 路径
  gSrcInFile = strFormat("%s/data/%d/%d.in", OJ_HOME, gProblemId, case_id);
  gDstInFile = strFormat("%s/run/%d/data.in", OJ_HOME, gRunnerId);
  gAndOutFile = strFormat("%s/data/%d/%d.out", OJ_HOME, gProblemId, case_id);
  gUserOutFile = strFormat("%s/run/%d/user.out", OJ_HOME, gRunnerId);

  // 2. 拷贝输入文件
  std::string cp_cmd = strFormat("/bin/cp %s %s", gSrcInFile.c_str(), gDstInFile.c_str());
  ret = system(cp_cmd.c_str());
  if (ret) {
    LOG_FATAL("cp failed %d, %s", ret, cp_cmd.c_str());
    return ret;
  }

  // 3. 检测输出文件
  if (access(gAndOutFile.c_str(), R_OK)) {
    LOG_FATAL("access(%s) failed", gAndOutFile.c_str());
    return ret;
  }

  return ret;
}

int runSolution(const char* work_dir) {
  int ret = 0;

  // FIXME: 开摆，不限制 JAVA 消耗的资源
  if (gLanguage != LANG_JAVA) {
    // https://www.jianshu.com/p/b1f9d6911c90
    ret = ptrace(PTRACE_TRACEME, 0, nullptr, nullptr);
    if (ret) {
      LOG_FATAL("trace me failed, ret %d", ret);
      return ret;
    }

    // TODO: chroot

    struct rlimit limit;
    // on-cpu time limit
    limit.rlim_max = RUN_TIME_LIMIT;
    limit.rlim_cur = RUN_TIME_LIMIT;
    ret = setrlimit(RLIMIT_CPU, &limit);
    if (ret) {
      LOG_FATAL("setrlimit(RLIMIT_CPU) fail, ret %d", ret);
      return ret;
    }

    // off-cpu time limit
    // 设置原因同 compile 函数
    alarm(RUN_TIME_LIMIT * 5);

    // mem limit
    // 这里不直接用最大值是因为一旦到上限就会触发 SIGKILL，
    // 而前面 RLIMIT_CPU 也会触发 SIGKILL，
    // 二者无法区分。
    // 所以这里只是比较宽松的限制下，配合后面的 rusage 一起判断
    limit.rlim_max = RUN_MEM_LIMIT * 2;
    limit.rlim_cur = RUN_MEM_LIMIT / 2 * 3;
    ret = setrlimit(RLIMIT_AS, &limit);
    if (ret) {
      LOG_FATAL("setrlimit(RLIMIT_AS) fail, ret %d", ret);
      return ret;
    }

    // proc num & thread num limit
    limit.rlim_max = 1;
    limit.rlim_cur = 1;
    ret = setrlimit(RLIMIT_NPROC, &limit);
    if (ret) {
      LOG_FATAL("setrlimit(RLIMIT_NPROC) fail, ret %d", ret);
      return ret;
    }

    // file limit
    limit.rlim_max = RUN_FILE_LIMIT;
    limit.rlim_cur = RUN_FILE_LIMIT;
    ret = setrlimit(RLIMIT_FSIZE, &limit);
    if (ret) {
      LOG_FATAL("setrlimit(RLIMIT_FSIZE) fail, ret %d", ret);
      return ret;
    }
  }

  // 到了要调用 exec 再 freopen，不然会把日志也重定向 user.out
  freopen("data.in", "r", stdin);
  freopen("user.out", "w", stdout);
  freopen("error.out", "w", stderr);

  // run solution
  ret = execvp(languages[gLanguage].run_cmd[0], (char* const*)languages[gLanguage].run_cmd);

  // 先关闭重定向才能正常打日志
  // https://blog.csdn.net/ShellDawn/article/details/114287019
  freopen("/dev/tty", "w", stdout);

  if (ret) {
    LOG_FATAL("execvp failed, ret %d, %s", ret, strerror(errno));
    return ret;
  }

  LOG_FATAL("should never run here");
}

void setJudgeResultBySignum(int sig) {
  switch (sig) {
    case SIGCHLD:
    case SIGALRM:
      LOGD("clean alarm");
      alarm(0);  // 清除闹钟，记录为超时
    case SIGKILL:
    case SIGXCPU:
      LOGD("get sig, out of time limit");
      gJudgeResult = OJ_TL;
      break;
    case SIGXFSZ:
      LOGD("get sig, out of file limit");
      gJudgeResult = OJ_OL;
      break;
    case SIGSEGV:
      LOGD("get sig, run time err");
      gJudgeResult = OJ_RE;
    default:
      // 理论上不该有其他的信号值，真遇到了算作 RE
      LOG_ERROR("get unknwon sig %d, just set RE", sig);
      gJudgeResult = OJ_RE;
  }
}

// 监控用户代码执行状态
int watchSolution(int case_id) {
  int ret = 0;

  int debug_run_counter = 0;  // 调试用的，统计后面的循环跑了多少次

  // LOGD("user code pid %d start watch", gUserPid);

  long max_mem_used = 0;
  long max_time_used = 0;

  struct rusage resuce_usage;

  while (true) {
    int user_status;
    // 等待唯一 child 结束运行
    ret = wait4(gUserPid, &user_status, __WALL, &resuce_usage);
    if (ret < 0) {
      LOG_FATAL("wait4 user code fail, ret %d", ret);
      return ret;
    } else if (ret > 0 && ret != gUserPid) {
      LOG_FATAL("wait4 get pid %d != user pid %d", ret, gUserPid);
      return ret;
    }

    // FIXME: 总之 java 一跑下面的代码就会 SIGSEGV，先跳过
    if (gLanguage == LANG_JAVA) {
      break;
    }

    // 仅在第一次执行时设置
    if (debug_run_counter == 0) {
      // TRACESYSGOOD: 使用系统调用时，将 signum 设为 SIGTRAP | 0x80
      // TRACEEXIT: 子进程退出时暂停住，此时可以通过 waitpid 拿到 exitcode
      ret = ptrace(PTRACE_SETOPTIONS, gUserPid, nullptr, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXIT);
      if (ret < 0) {
        // trace 失败可能是因为 user code 没有做 syscall，不影响正常运行
        LOG_ERROR("ptrace failed, ret %d, %s", ret, strerror(errno));
      }
    }

    // 内存检测
    // 配合前面的 RLIMIT_AS
    // 一个设置上限，一个检测是否达到设置的上限
    long used_mem = getpagesize() * resuce_usage.ru_minflt;
    // LOGD("user code used mem %ld MB", used_mem / 1000 / 1000);
    if (used_mem > RUN_MEM_LIMIT) {
      LOGD("user code out of mem %ld MB", used_mem / 1024 / 1024);
      gJudgeResult = OJ_ML;
      break;
    }
    // LOGD("current used memory: %ldKB", used_mem / 1024);
    if (used_mem > max_mem_used) {
      max_mem_used = used_mem;
    }

    // 参考 man 2 waitpid，分三种情况 WIFEXITED / WIFSIGNALED / WIFSTOPPED
    // 已经正常退出
    if (WIFEXITED(user_status)) {
      LOGD("user code succ exit");
      // https://blog.csdn.net/fallenink/article/details/8477277
      // exitcode 可以手动设置，也可在收到 signal 时自动设置
      // 退出时未发生 coredump：exitcode 为 signum
      // 发生 coredump： exitcode 为 signum + 128
      int exitcode = WEXITSTATUS(user_status);
      if (exitcode != 0) {
        LOG_FATAL("unknown user code exitcode %d", exitcode);
        // return exitcode;
      }
      break;
    }
    // 因为信号退出
    else if (WIFSIGNALED(user_status)) {
      int sig = WTERMSIG(user_status);
      LOGD("user code terminated by sig %d: %s", sig, strsignal(sig));
      setJudgeResultBySignum(sig);
      // 只要触发了信号最后都要退出
      break;
    }
    // 未退出，只是因为信号暂停了
    // WIFSTOPPED: returns true if the child process was stopped by delivery of a signal;
    // this is only possible if the call was done using WUNTRACED
    // or when the child is being traced (see ptrace(2)).
    else if (WIFSTOPPED(user_status)) {
      int sig = WSTOPSIG(user_status);
      // 这两种信号因为 ptrace 引起，所以不该退出
      // 5: SIGTRAP
      // 133: SIGTRAP + 128
      // 详细的 signal 号可以查询 man 7 signal
      if (sig != 5 && sig != 133) {
        LOGD("user code stopped by sig %d: %s", sig, strsignal(sig));
        setJudgeResultBySignum(sig);
        // 只要触发了信号最后都要退出
        break;
      }
    }

    // 文件体积过大
    if (getFileSize(gUserOutFile.c_str()) > RUN_FILE_LIMIT) {
      LOGD("user code out of file limit");
      gJudgeResult = OJ_OL;
      break;
    }

    // 检测系统调用
    struct user_regs_struct reg;
    ret = ptrace(PTRACE_GETREGS, gUserPid, nullptr, &reg);
    if (ret < 0) {
      // trace 失败可能是因为 user code 没有做 syscall，不影响正常运行
      LOG_ERROR("ptrace failed, ret %d, %s", ret, strerror(errno));
    }
    uint call_id = (uint)reg.REG_SYSCALL;
    // LOGD("user code syscall %d", call_id);
    if (call_id > CALL_ARRAY_SIZE || call_id < 0) {
      LOG_ERROR("call_id > CALL_ARRAY_SIZE %d", call_id);
    } else if (!syscall_white_map[call_id]) {
      LOGD("used a not allow syscall, id %d", call_id);
      gJudgeResult = OJ_RE;
      break;
    }
    ret = ptrace(PTRACE_SYSCALL, gUserPid, NULL, NULL);
    if (ret < 0) {
      // trace 失败可能是因为 user code 没有做 syscall，不影响正常运行
      LOG_ERROR("ptrace failed, ret %d, %s", ret, strerror(errno));
    }

    // LOGD("current used time %lds %ldms", resuce_usage.ru_utime.tv_sec, resuce_usage.ru_utime.tv_usec);
    if (resuce_usage.ru_utime.tv_usec > max_time_used) {
      max_time_used = resuce_usage.ru_utime.tv_usec;
    }

    debug_run_counter++;
  }

  // LOGD("debug run counter: %d", debug_run_counter);

  if (gJudgeResult != OJ_RI) {
    // 已经确定结果：杀掉子进程
    ret = ptrace(PTRACE_KILL, gUserPid, nullptr, nullptr);
    if (ret < 0) {
      // trace 失败可能是因为 user code 没有做 syscall，不影响正常运行
      LOG_ERROR("ptrace failed, ret %d, %s", ret, strerror(errno));
    }
  } else {
    // 未确定结果

    //正常退出,不需要 ptrace kill 子进程

    //TODO: 这里再次判定其实是为了上报 solution 执行时间用的，但上报的代码还没加
    time_t used_time = resuce_usage.ru_utime.tv_sec * 1000 + resuce_usage.ru_utime.tv_usec / 1000;
    // LOGD("user code used time: %ld", used_time);
    LOGD("user code total used time %ldms, max used memory %ldKB", max_time_used, max_mem_used / 1024);
    if (gLanguage == LANG_PYTHON || gLanguage == LANG_JAVA) {
      // FIXME: 对于 PYTHON，测出来的时间总是 10+ 甚至 20+，这里放宽下限制
      if (used_time > RUN_TIME_LIMIT * 30) {
        LOGD("user code TLE %ld > %d", used_time, RUN_TIME_LIMIT);
        gJudgeResult = OJ_TL;
        return 0;
      }
    } else {
      if (used_time > RUN_TIME_LIMIT) {
        LOGD("user code TLE %ld > %d", used_time, RUN_TIME_LIMIT);
        gJudgeResult = OJ_TL;
        return 0;
      }
    }

    int wa = diff(gUserOutFile.c_str(), gAndOutFile.c_str());
    if (wa) {
      LOGD("user code WA, diff ret %d, user_out %s ans_out %s", wa, gUserOutFile.c_str(), gAndOutFile.c_str());
      gJudgeResult = OJ_WA;
    } else {
      // 这里 pass 不可以设置 AC，要等所有 test case 跑完才能设置
      // LOGD("user code pass test case %d", case_id);
    }
  }
  return 0;
}

// 执行一个 solution 下的单个 test case
int runOneTestCase(int case_id) {
  // LOGD("run one test case pid %d cid %d rid %d", problem_id, case_id, gRunnerId);

  int ret = 0;
  initSyscallCouter();
  ret = prepareTestCaseDataFile(case_id);
  if (ret != 0) {
    LOG_FATAL("prepareTestCaseDataFile fail, ret %d", ret);
    return ret;
  }

  gUserPid = fork();
  if (gUserPid == 0) {
    ret = runSolution(gUserCodeWorkDir.c_str());
    if (ret) {
      LOGD("run solution failed");
    }
  } else {
    ret = watchSolution(case_id);
    if (ret) {
      LOGD("watch solution failed");
    }
  }
  return 0;
}

// 评测一个 solution
int judgeOneSolution() {
  int ret = 0;

  std::string test_data_path = strFormat("%s/data/%d", OJ_HOME, gProblemId);
  DIR* dp = opendir(test_data_path.c_str());
  if (dp == nullptr) {
    gJudgeResult = OJ_SE;
    LOG_FATAL("no such test data dir: %s", test_data_path.c_str());
    exit(-1);
  }

  // 获取评测点总数
  struct dirent** filename_list;
  gTotalTestCount = scandir(test_data_path.c_str(), &filename_list, inputFileFilter, versionsort);
  if (gTotalTestCount <= 0) {
    gJudgeResult = OJ_SE;
    LOG_FATAL("found test data dir: %s, but no input files", test_data_path.c_str());
    exit(-1);
  }

  // 通过的测试点计数器
  gPassedTestCount = 0;
  // 注意：case_id 从 1 开始
  for (int case_id = 1; case_id <= gTotalTestCount; case_id++) {
    LOGD("start judge test case %d / %d", case_id, gTotalTestCount);
    ret = runOneTestCase(case_id);
    if (ret) {
      LOG_FATAL("run test case failed, p %d c %d ret %d", gProblemId, case_id, ret);
      return ret;
    }
    if (gJudgeResult != OJ_RI) {
      // 评测结果已出，可能是 TLE、WA、RE 或者别的，总之不是 AC
      // 剩下的 test case 不用判断了，直接返回
      return 0;
    }

    gPassedTestCount++;
    reportJudgerStatusToMq(false);
  }

  // 所有评测点都通过才算作 AC
  LOGD("All test case run over");
  gJudgeResult = OJ_AC;
  return 0;
}

static void usage(const char* prog_name, const char* msg) {
  if (msg != nullptr) fputs(msg, stderr);

  fprintf(stderr, "Usage: %s -r <runner_id> -p <problem_id> -s <solution_id> -c <user_code_path>\n", prog_name);
  exit(EXIT_FAILURE);
}

void parseCmdArg(int argc, char* argv[]) {
  int ret = 0;

  int opt;
  while ((opt = getopt(argc, argv, "r:p:s:c:l:")) != -1) {
    switch (opt) {
      case 'r':
        ret = str2int(optarg, &gRunnerId);
        break;
      case 'p':
        ret = str2int(optarg, &gProblemId);
        break;
      case 's':
        ret = str2int(optarg, &gSolutionId);
        break;
      case 'c':
        gUserCodeSrcPath = std::string(optarg);
        break;
      case 'l':
        ret = str2int(optarg, &gLanguage);
        break;
      default:
        usage(argv[0], "Unrecognized option\n");
    }
    if (ret) {
      printf("parse argument failed, please check!\n");
      exit(EXIT_FAILURE);
    }
  }

  // runner id 可以等于 0，其他两个不能等于 0
  if (gRunnerId < 0 || gProblemId <= 0 || gSolutionId <= 0 || gUserCodeSrcPath.size() == 0 || gLanguage == 0) {
    // printf("argment illegal, rid %d pid %d sid %d usercodepath %s\n", gRunnerId, gProblemId, gSolutionId,
    // gUserCodeSrcPath.c_str());
    usage(argv[0], "Argument err!\n");
    exit(EXIT_FAILURE);
  }
}

// 初始化全局参数
void initGlobalArgs() {
  // 用户代码路径时恒定的

  auto* ptr = getcwd(OJ_HOME, sizeof(OJ_HOME));
  if (ptr == nullptr) {
    LOG_FATAL("getcwd failed, %s", strerror(errno));
    exit(-1);
  }

  gUserCodeWorkDir = strFormat("%s/run/%d", OJ_HOME, gRunnerId);
  gJudgeResult = OJ_WT0;

  LOGD("init: gRunnerId %d gProblemId %d gSolutionId %d gJudgeResult %d gUserCodeWorkDir %s gUserCodeSrcPath %s",
       gRunnerId, gProblemId, gSolutionId, gJudgeResult, gUserCodeWorkDir.c_str(), gUserCodeSrcPath.c_str());
}

// 初始化用户代码工作路径
int prepareUserCodeWorkDir() {
  int ret = 0;

  // 1. 创建工作目录
  std::string mk_cmd = strFormat("mkdir -p %s", gUserCodeWorkDir.c_str());
  ret = system(mk_cmd.c_str());
  if (ret) {
    LOG_FATAL("mkdir %s failed, ret %d", mk_cmd.c_str(), ret);
    return ret;
  }

  // 切换位置
  ret = chdir(gUserCodeWorkDir.c_str());
  if (ret) {
    LOG_FATAL("chdir fail, work_dir %s ret %d", gUserCodeWorkDir.c_str(), ret);
    return ret;
  }

  // 目前先不清理了，rm 太危险了，别把我自己的文件误删了

  // 移动代码文件
  // 一个潜在 bug：代码执行到这里前卡了一会儿，backend 认为 judger 异常，重新发了一份 solution，
  // dispatcher 又分配了一个 judger。造成两个 judger /bin/mv 同一份代码
  // 不过目前先不考量这种极端情况

  // 算了，改成 cp 吧，这样就不需要操心了
  // 这些代码不删除也不会占用太大空间，以 QLU OJ 为例，一年多交了 4W+ 份代码，每份算 2K 也只有 80MB
  std::string cmd_str = strFormat("/bin/cp %s %s/Main.%s", gUserCodeSrcPath.c_str(), gUserCodeWorkDir.c_str(),
                                  languages[gLanguage].file_ext);
  ret = system(cmd_str.c_str());
  if (ret) {
    LOG_FATAL("copy user code failed, ret %d, %s", ret, cmd_str.c_str());
    return ret;
  }
  return 0;
}

int main(int argc, char* argv[]) {
  int ret = 0;

  parseCmdArg(argc, argv);

  // 初始化 mq
  mq_id = msgget(MSG_KEY, IPC_CREAT | 0666);
  if (mq_id == -1) {
    LOG_FATAL("msgget fail, %s", strerror(errno));
    exit(EXIT_FAILURE);
  }

  initGlobalArgs();
  ret = prepareUserCodeWorkDir();
  if (ret) {
    return ret;
  }

  int cc_result = compile(gUserCodeWorkDir.c_str());
  if (gJudgeResult == OJ_WT0 || gJudgeResult == OJ_WT1) {
    // compile 完成后不可能还处于 wait 阶段，必然是出问题了
    LOG_FATAL("judge result illegal %d after compile", gJudgeResult);
    exit(-1);
  } else if (cc_result != 0) {
    // 编译失败
    LOGD("compile failed");
  } else {
    // cc_result == 0，编译成功
    ret = judgeOneSolution();
    if (ret) {
      LOG_ERROR("judgeOneSolution failed, ret %d", ret);
      exit(-1);
      return ret;
    }
  }

  LOGD("judger succ exit, judge result %s", oj_err_code_map[gJudgeResult].c_str());

  reportJudgerStatusToMq(true);

  return 0;
}

// TODO: OJ_SE 终止运行则不会返回评测结果，
//  那么在 dispatch 中等待超时则视为评测失败

/*
judger 的单元测试用例
1. 单个 test case 的 a + b 问题的 AC 测试
2. 多个 test case 的 a + b + c 问题的 AC 测试
3. CE 测试：故意把 Main.cpp 代码改错
4. on-cpu TLE 测试：搞个 60 层的递归 Fibonacci
5. off-cpu TLE 测试：sleep(10)
6. MLE 测试：多分配点内存
7. file size out limit： 拼命输出到 stdout 里
8. 编译期炸弹
*/